from flask import Flask, request, jsonify, send_file
import serial
import struct
import time
import threading
import random
from datetime import datetime
import os
from threading import Lock
from flask_cors import CORS

# 全局配置
app = Flask(__name__)
CORS(app)  # 解决跨域问题
app.config['JSON_AS_ASCII'] = False  # 支持中文
CORS(app)  # 解决跨域问题

# 全局变量
旋转360度数据 = []  # 存储连续测量数据
data_lock = Lock()  # 数据操作锁
motor_instance = None  # 电机设备实例
measurement_thread = None  # 测量线程
is_measuring = False  # 测量状态标记

class LaserSensor:
    """激光传感器类（虚拟/真实适配）"""
    def __init__(self, sensor_id):
        self.sensor_id = sensor_id  # 3=50mm传感器，4=100mm传感器
        self.base_distance = 25.0 if sensor_id == 3 else 60.0  # 基准距离
    
    def read(self):
        """读取传感器距离（带±0.5mm波动，模拟真实数据）"""
        return round(self.base_distance + random.uniform(-0.5, 0.5), 5)

class HaitaiMotor:
    """海泰电机+激光传感器控制类"""
    def __init__(self, port="COM4", baudrate=115200):
        self.port = port
        self.baudrate = baudrate
        self.ser = None
        self.lock = Lock()  # 串口操作锁
        self.current_sensor = None  # 当前选中的传感器
        self.connected = False  # 设备连接状态
        self.current_angle = 0.0  # 缓存当前角度（用于/status接口）

    def _crc16_modbus(self, data):
        """Modbus CRC16校验（与虚拟串口完全一致）"""
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc & 0xFFFF

    def connect(self):
        """连接串口设备（电机+传感器）"""
        try:
            # 关闭已有连接
            if self.ser and self.ser.is_open:
                self.ser.close()
            
            # 初始化串口
            self.ser = serial.Serial(
                port=self.port,
                baudrate=self.baudrate,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                timeout=0.5,  # 延长超时，确保响应读取
                rtscts=False,
                dsrdtr=False
            )
            
            if self.ser.is_open:
                self.connected = True
                self.current_sensor = LaserSensor(3)  # 默认选中50mm传感器
                print(f"✅ 成功连接设备：{self.port}（波特率：{self.baudrate}）")
                # 读取一次初始角度
                self._read_current_angle()
                return True
            return False
        except Exception as e:
            print(f"❌ 设备连接失败：{str(e)}")
            self.connected = False
            return False

    def _send_command(self, cmd, data_bytes=b''):
        """发送指令并返回有效响应（修复超时、CRC校验）"""
        if not self.connected or not self.ser:
            print("❌ 设备未连接，无法发送指令")
            return None
        
        try:
            with self.lock:
                # 构建指令包（自定义协议：0x3E + 序号 + 电机ID + 指令 + 数据长度 + 数据 + CRC）
                seq = 0x00  # 序号固定（虚拟串口回显，不影响功能）
                motor_id = 0x01
                data_len = len(data_bytes)
                header = bytes([0x3E, seq, motor_id, cmd, data_len])
                full_cmd = header + data_bytes
                
                # 添加CRC校验
                crc = self._crc16_modbus(full_cmd)
                full_cmd += struct.pack('<H', crc)
                
                # 发送指令
                self.ser.flushInput()  # 清空输入缓冲区
                self.ser.write(full_cmd)
                self.ser.flush()
                print(f"📤 发送指令：{full_cmd.hex()}")
                
                # 等待响应（给虚拟串口处理时间）
                time.sleep(0.05)
                if self.ser.in_waiting > 0:
                    response = self.ser.read(self.ser.in_waiting)
                    print(f"📥 收到响应：{response.hex()}")
                    
                    # 验证响应合法性（协议头+长度+CRC）
                    if len(response) >= 15 and response[0] == 0x3C:  # 响应头0x3C，最小15字节
                        response_data = response[:-2]  # 去掉CRC字段
                        response_crc = struct.unpack('<H', response[-2:])[0]
                        calc_crc = self._crc16_modbus(response_data)
                        
                        if response_crc == calc_crc:
                            return response_data  # 返回有效响应（去掉CRC）
                        else:
                            print(f"❌ CRC校验失败：收到0x{response_crc:04X}，计算0x{calc_crc:04X}")
                    else:
                        print(f"❌ 响应非法：长度{len(response)}，协议头0x{response[0]:02X}")
                else:
                    print(f"❌ 发送指令后未收到响应")
            return None
        except Exception as e:
            print(f"❌ 指令发送失败：{str(e)}")
            return None

    def _read_current_angle(self):
        """读取当前电机角度（内部调用，更新缓存）"""
        response = self._send_command(0x2F)  # 0x2F=读取角度指令
        if response and len(response) >= 13:
            # 解析：单圈角度(2字节) + 多圈角度(4字节) + 保留(2字节)
            single_turn = struct.unpack('<H', response[5:7])[0]  # 第5-6字节（14位有效）
            multi_turn = struct.unpack('<I', response[7:11])[0]  # 第7-10字节
            # 计算实际角度：多圈*360 + 单圈*(360/16384)
            angle = (multi_turn * 360.0) + (single_turn * (360.0 / 16384.0))
            self.current_angle = round(angle % 360.0, 2)  # 归一化到0-360度
            print(f"📐 当前电机角度：{self.current_angle}°")
        return self.current_angle

    def set_sensor(self, sensor_id):
        """切换激光传感器（3=50mm，4=100mm）"""
        if sensor_id not in [3, 4]:
            return False, "无效传感器ID（仅支持3=50mm/4=100mm）"
        self.current_sensor = LaserSensor(sensor_id)
        print(f"🔄 切换传感器：{'50mm' if sensor_id ==3 else '100mm'}")
        return True, f"切换到{sensor_id}号传感器成功"

    def start_360_measurement(self):
        """启动360度连续测量（线程安全）"""
        global 旋转360度数据, is_measuring
        if not self.connected:
            return False, "设备未连接"
        if is_measuring:
            return False, "正在测量中，请勿重复启动"
        
        # 清空历史数据
        with data_lock:
            旋转360度数据.clear()
        
        is_measuring = True
        print("🚀 启动360度连续测量...")
        
        try:
            # 1. 设置电机速度（500）
            speed_resp = self._send_command(0x57, b'\x01\xF4\x01')
            if not speed_resp:
                is_measuring = False
                return False, "设置电机速度失败"
            time.sleep(0.2)
            
            # 2. 设置目标角度360度（计数=16384）
            target_counts = 16384
            angle_resp = self._send_command(0x55, struct.pack('<I', target_counts))
            if not angle_resp:
                is_measuring = False
                return False, "设置目标角度失败"
            time.sleep(0.2)
            
            # 3. 读取初始角度（校准基准）
            initial_angle = self._read_current_angle()
            if initial_angle is None:
                is_measuring = False
                return False, "读取初始角度失败"
            
            # 4. 持续采集数据
            start_time = time.time()
            max_duration = 60  # 最大测量时长60秒
            rotated = False  # 标记是否开始旋转
            complete_count = 0  # 完成旋转的确认次数
            
            while time.time() - start_time < max_duration and is_measuring:
                # 读取当前角度
                current_angle = self._read_current_angle()
                
                # 读取传感器距离
                distance = self.current_sensor.read() if self.current_sensor else 0.0
                
                # 记录数据（线程安全）
                with data_lock:
                    旋转360度数据.append({
                        "系统时间": datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3],
                        "电机角度(°)": current_angle,
                        "距离(mm)": distance
                    })
                
                # 打印实时状态
                print(f"📊 [采集] 角度：{current_angle}° | 距离：{distance}mm | 条数：{len(旋转360度数据)}")
                
                # 判断是否开始旋转（角度变化>1°）
                if not rotated and abs(current_angle - initial_angle) > 1.0:
                    rotated = True
                    print(f"✅ 电机开始旋转，初始角度：{initial_angle}°")
                
                # 判断是否完成旋转（已旋转+角度回到初始值±2°，连续3次确认）
                if rotated and abs(current_angle - initial_angle) < 2.0:
                    complete_count += 1
                    if complete_count >= 3:
                        print("🎉 360度旋转测量完成！")
                        break
                else:
                    complete_count = 0  # 重置确认次数
                
                time.sleep(0.1)  # 10Hz采集频率
            
            # 5. 停止电机
            self._send_command(0x50)
            is_measuring = False
            
            # 6. 保存数据到CSV
            if len(旋转360度数据) > 0:
                self.save_measurement_data()
            
            return True, f"测量完成，共采集{len(旋转360度数据)}条数据"
        except Exception as e:
            is_measuring = False
            print(f"❌ 测量过程异常：{str(e)}")
            return False, f"测量失败：{str(e)}"

    def stop_measurement(self):
        """停止测量（紧急中断）"""
        global is_measuring
        is_measuring = False
        # 停止电机
        if self.connected:
            self._send_command(0x50)
        print("🛑 测量已停止")
        return True, "测量停止成功"

    def save_measurement_data(self, filename=None):
        """保存测量数据到CSV文件"""
        global 旋转360度数据
        if not 旋转360度数据:
            print("❌ 无测量数据可保存")
            return False, "无数据"
        
        # 生成文件名（时间戳）
        if not filename:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"360度测量数据_{timestamp}.csv"
        
        try:
            with data_lock:
                with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
                    # 写入表头
                    f.write("系统时间,电机角度(°),距离(mm)\n")
                    # 写入数据
                    for data in 旋转360度数据:
                        f.write(f"{data['系统时间']},{data['电机角度(°)']},{data['距离(mm)']}\n")
            print(f"💾 数据已保存到：{os.path.abspath(filename)}")
            return True, filename
        except Exception as e:
            print(f"❌ 保存数据失败：{str(e)}")
            return False, f"保存失败：{str(e)}"

    def disconnect(self):
        """断开设备连接"""
        global is_measuring
        is_measuring = False
        if self.ser and self.ser.is_open:
            self.ser.close()
        self.connected = False
        print("🔌 设备已断开连接")
        return True, "断开连接成功"

# ---------------------- Flask API接口 ----------------------
@app.route('/api/connect', methods=['POST'])
def api_connect():
    """连接设备接口"""
    global motor_instance
    data = request.json or {}
    port = data.get('port', 'COM4')
    baudrate = data.get('baudrate', 115200)
    
    # 初始化电机实例
    if not motor_instance:
        motor_instance = HaitaiMotor(port=port, baudrate=baudrate)
    else:
        motor_instance.port = port
        motor_instance.baudrate = baudrate
    
    success = motor_instance.connect()
    return jsonify({
        "success": success,
        "message": "连接成功" if success else "连接失败",
        "connected": motor_instance.connected
    })

@app.route('/api/disconnect', methods=['POST'])
def api_disconnect():
    """断开设备接口"""
    global motor_instance
    if not motor_instance:
        return jsonify({"success": False, "message": "未初始化设备"})
    
    success, msg = motor_instance.disconnect()
    return jsonify({"success": success, "message": msg})

@app.route('/api/status', methods=['GET'])
def api_status():
    """获取设备状态接口（实时角度、测量状态等）"""
    global motor_instance, is_measuring, 旋转360度数据
    if not motor_instance:
        return jsonify({
            "connected": False,
            "is_measuring": False,
            "current_angle": 0.0,
            "data_count": 0,
            "sensor": "未初始化"
        })
    
    # 实时更新角度
    current_angle = motor_instance._read_current_angle()
    sensor_type = "50mm" if motor_instance.current_sensor and motor_instance.current_sensor.sensor_id ==3 else "100mm"
    
    return jsonify({
        "connected": motor_instance.connected,
        "is_measuring": is_measuring,
        "current_angle": current_angle,
        "data_count": len(旋转360度数据),
        "sensor": sensor_type,
        "message": "状态正常" if motor_instance.connected else "设备未连接"
    })

@app.route('/api/set-sensor', methods=['POST'])
def api_set_sensor():
    """切换传感器接口"""
    global motor_instance
    if not motor_instance or not motor_instance.connected:
        return jsonify({"success": False, "message": "设备未连接"})
    
    data = request.json or {}
    sensor_id = data.get('sensor_id', 3)
    success, msg = motor_instance.set_sensor(sensor_id)
    return jsonify({"success": success, "message": msg})

@app.route('/api/start', methods=['POST'])
def api_start_measurement():
    """启动360度测量接口"""
    global motor_instance, measurement_thread
    if not motor_instance or not motor_instance.connected:
        return jsonify({"success": False, "message": "设备未连接"})
    
    if is_measuring:
        return jsonify({"success": False, "message": "正在测量中，请勿重复启动"})
    
    # 启动测量线程（避免阻塞API）
    def measurement_task():
        success, msg = motor_instance.start_360_measurement()
        print(f"测量线程结果：{success} - {msg}")
    
    measurement_thread = threading.Thread(target=measurement_task, daemon=True)
    measurement_thread.start()
    
    return jsonify({"success": True, "message": "测量已启动，请通过/status接口查看进度"})

@app.route('/api/stop', methods=['POST'])
def api_stop_measurement():
    """停止测量接口"""
    global motor_instance
    if not motor_instance or not motor_instance.connected:
        return jsonify({"success": False, "message": "设备未连接"})
    
    success, msg = motor_instance.stop_measurement()
    return jsonify({"success": success, "message": msg})

@app.route('/api/download', methods=['GET'])
def api_download_data():
    """下载测量数据CSV接口"""
    global 旋转360度数据
    if not 旋转360度数据:
        return jsonify({"success": False, "message": "无测量数据"}), 404
    
    # 生成临时CSV文件
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    filename = f"360度测量数据_{timestamp}.csv"
    
    success, file_path = motor_instance.save_measurement_data(filename)
    if not success:
        return jsonify({"success": False, "message": file_path}), 500
    
    # 发送文件
    return send_file(
        file_path,
        as_attachment=True,
        download_name=os.path.basename(file_path),
        mimetype='text/csv'
    )

@app.route('/api/clear-data', methods=['POST'])
def api_clear_data():
    """清空测量数据接口"""
    global 旋转360度数据
    with data_lock:
        旋转360度数据.clear()
    return jsonify({"success": True, "message": "数据已清空"})

# ---------------------- 程序入口 ----------------------
if __name__ == "__main__":
    # 初始化电机实例（默认参数，可通过API修改）
    motor_instance = HaitaiMotor(port="COM4", baudrate=115200)
    
    print("="*50)
    print("🎯 激光位移360度测量系统 - 后端服务")
    print(f"📅 启动时间：{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"🔧 默认串口：COM3（波特率：115200）")
    print("📡 API地址：http://127.0.0.1:5000")
    print("="*50)
    
    # 启动Flask服务（允许跨域，方便前端调用）
    from flask_cors import CORS
    CORS(app, resources=r"/*")  # 允许所有跨域请求
    app.run(host='0.0.0.0', port=5000, debug=False)  # 生产环境关闭debug